Android 开发中,加载图片的一些代码笔记。

计算源图片宽高

图片源文件和目标 View 之间,大部分情况下,不会完全地等宽等高,所以需要对图片进行压缩。

压缩图片需要用到 BitmapFactory.Options,具体用法如下。

1
2
3
4
5
6
7
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
// decodeFile() 方法的参数很多,接收不同的图片来源。
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
// 源图片的高度和宽度
int sourceHeight = options.outHeight;
int sourceWidth = options.outWidth;

其中

1
options.inJustDecodeBounds = true;

设置为 inJustDecodeBounds 为 true 表示不把整个图片加载进内存中,只是测量其宽高,MIME TYPE 等信息。

计算压缩比例

如果目标的 View 比源图片小,就需要对图片进行压缩,可以通过以下代码片段计算出缩放比例。

1
2
3
4
5
if (sourceHeight > targetHeight || sourceWidth > targetWidth) {
int heightRatio = Math.round((float) sourceHeight / (float) targetHeight);
int widthRatio = Math.round((float) sourceWidth / (float) targetWidth);
options.inSampleSize = Math.max(heightRatio, widthRatio);
}

计算出 inSampleSize 之后,不再需要测量图片,而是要加载图片,将 inJustDecodeBounds 设置为 false

1
options.inJustDecodeBounds = false;

编码图片,这里的 inSampleSize 只会取最近的一个 2 的整数次幂(向下取整),比如计算出来的 inSampleSize 是 7,那么实际上是 4.

也就是宽和高同时除以 2,整个面积变为原来的 1/4.

1
2
3
FileInputStream fileInputStream = new FileInputStream(file);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
return BitmapFactory.decodeStream(bufferedInputStream, null, options);

这样子编码出来的 bitmap 对象,所占的内存就是参照 目标 View 计算出来的,相对合理。

再次压缩图片

由于 inSampleSize 参数的限制,所以,压缩后的图片不一定是效果最好的(一般情况下都不是),这个时候, bitmap 所占的内存已经比较小了,可以使用 createScaleBitmap 方法再次进行缩放,这个操作是 bitmap 上进行操作的,所以压缩返回新对象之后,需要将原来的 bitmap 对象回收。

1
2
3
4
5
6
7
private static Bitmap createScaleBitmap(Bitmap src, int targetWidth, int targetHeight) {
Bitmap dst = Bitmap.createScaledBitmap(src, targetWidth, targetHeight, false);
if (src != dst) {
src.recycle();
}
return dst;
}

获取 View 的宽高

获取 View 的宽高时候,可能会出现取值为 0 的情况,出现这种情况,可能的原因是:整个 ViewTree 并没有绘制完成。

参考一个 stackoverflow 上的回答,对 ViewTree 进行监听,当绘制完成之后,再获取 View 的宽高。

1
2
3
4
5
6
7
8
9
final TextView tv = (TextView)findViewById(R.id.venueLabel);
final ViewTreeObserver observer= tv.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
tv.getHeight();
observer.removeGlobalOnLayoutListener(this);
}
});